package cn.lingyangwl.agile.infra.gen.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.agile.infra.datamanage.assembly.*;
import cn.lingyangwl.agile.infra.datamanage.db.*;
import cn.lingyangwl.agile.infra.datamanage.mode.entity.*;
import cn.lingyangwl.agile.infra.datamanage.service.*;
import cn.lingyangwl.agile.infra.gen.assembly.*;
import cn.lingyangwl.agile.infra.gen.config.*;
import cn.lingyangwl.agile.infra.gen.constants.*;
import cn.lingyangwl.agile.infra.gen.manager.*;
import cn.lingyangwl.agile.infra.gen.mapper.*;
import cn.lingyangwl.agile.infra.gen.model.*;
import cn.lingyangwl.agile.infra.gen.model.entity.*;
import cn.lingyangwl.agile.infra.gen.model.rqrs.*;
import cn.lingyangwl.agile.infra.gen.service.*;
import cn.lingyangwl.agile.infra.gen.utils.*;
import cn.lingyangwl.agile.infra.module.datamanage.db.*;
import cn.lingyangwl.agile.model.*;
import cn.lingyangwl.framework.core.utils.spring.*;
import cn.lingyangwl.framework.tool.core.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import cn.lingyangwl.framework.tool.core.file.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.CaseFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTable> implements IGenTableService {
    @Resource
    private DocManager docManager;
    @Resource
    private GenTableManager genTableManager;
    @Resource
    private GenTableColumnManager genTableColumnManager;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private DatasourceService datasourceService;
    @Resource
    private GenTableAssembly genTableAssembly;
    @Resource
    private DatasourceConfigAssembly datasourceConfigAssembly;
    @Resource
    private GenTableIndexManager genTableIndexManager;

    private final Map<DbType, DocumentTableHandler> tableDialectHandlerMap = new ConcurrentHashMap<>();

    private GenSqlConfig genSqlConfig;

    @PostConstruct
    public void init() {
        genSqlConfig = new GenSqlConfig();
        List<String> extendColumnList = new ArrayList<>();
        extendColumnList.add("`patientId` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '病人信息表的主键id，没有时默认为0'");
        genSqlConfig.setExtendColumn(extendColumnList);

        applicationContext.getBeansOfType(DocumentTableHandler.class)
                .forEach((k, v) -> tableDialectHandlerMap.put(v.dbType(), v));
    }

    /**
     * 初始化表格的索引信息, 为了读取表格中数据 (必填)
     */
    private Map<String, Integer> initRequiredTableIndexMap(ImportDocumentTableResp req) {
        Map<String, Integer> indexMap = new HashMap<>();
        indexMap.put(req.getColumnCommentCol(), -1);
        indexMap.put(req.getColumnNameCol(), -1);
        indexMap.put(req.getColumnTypeCol(), -1);
        indexMap.put(req.getRequiredCol(), -1);
        indexMap.put(req.getTableIndexCol(), -1);
        return indexMap;
    }

    private void checkRequiredTableIndexMap(Map<String, Integer> indexMap) {
        Set<String> collect = indexMap.entrySet().stream().filter(e -> e.getValue() == -1)
                .map(Map.Entry::getKey).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(collect)) {
            throw new BizException("文档中表列名不包含所指定的列名: {}", collect);
        }
    }

    @Override
    @Transactional
    public List<ImportTableResp> importWordTable(ImportDocumentTableResp req, WordTableManager wordTableManager) {
        DatasourceConfig datasourceConfig = datasourceService.getById(req.getDatasourceId());
        Assert.notNull(datasourceConfig, "数据源不存在");
        GeneratorConfig generatorConfig = datasourceConfigAssembly.toEntity(datasourceConfig);
        DocumentTableHandler documentTableHandler = DocumentTableFactory.build(generatorConfig);
        File localFile = new File(req.getFilePath());
        try {
            List<WordDataUnderTitle> wordDataList = wordTableManager.getWordDataList(localFile.getAbsolutePath());
            Map<String, Integer> requiredIndexMap = initRequiredTableIndexMap(req);

            List<ImportTable> importTableList = new CopyOnWriteArrayList<>();

            // 遍历工作簿中的所有数据
            Stream<WordDataUnderTitle> stream = wordDataList.stream().filter(x -> x.getTableNum() > 0);
            stream.forEach((item) -> {
                String title = StringUtils.deleteWhitespace(item.getTitle().trim());
                String tableName = TableUtils.getTableName(item.getTitle().trim());
                if (StringUtils.isEmpty(tableName)) {
                    log.error("title: {}", title);
                    throw new BizException("word中有多余表格或者有非法标题命名方式, title: {}", title);
                }
                WordTable wordTable = item.getWordTableList().get(0);
                ImportTable importTable = new ImportTable();
                GenTable genTable = GenTable.builder()
                        .tplCategory(GenType.CRUD.getCode()).datasourceId(datasourceConfig.getId())
                        .functionName(title.replace(tableName, "")).tableName(tableName)
                        // 标题中表名去掉就相当于表描述了
                        .tableComment(title.replace(tableName, "")).build();
                importTable.setGenTable(genTable);
                // 创建主键列
                GenTableColumn pkColumn = GenTableColumn.builder()
                        .columnComment("主键").columnLength(req.getPkLength()).isPk(true)
                        .columnName(req.getPkName()).columnType(req.getPkType()).isNullable(false)
                        .columnScale(0).columnLength(req.getPkLength()).sort(1).build();
                importTable.getGenTableColumnList().add(pkColumn);

                int maxRow = wordTable.getRows().size();

                for (int row = 0; row < maxRow; row++) {
                    TableColumn column = new TableColumn();
                    Map<String, Consumer<String>> setMap = new HashMap<>();
                    setMap.put(req.getColumnCommentCol(), column::setColumnComment);
                    setMap.put(req.getColumnTypeCol(), column::setColumnType);
                    setMap.put(req.getColumnNameCol(), column::setColumnName);
                    setMap.put(req.getRequiredCol(), column::setIsRequiredFlag);

                    // 获取最后单元格num，即总单元格数 ***注意：此处从1开始计数***
                    int maxRol = wordTable.getRows().get(row).getCells().size();
                    for (int rol = 0; rol < maxRol; rol++) {
                        String data = wordTable.getRows().get(row).getCells().get(rol).getText();
                        if (StringUtils.isEmpty(data)) {
                            continue;
                        }
                        int finalRol = rol;
                        if (row == 0) {
                            requiredIndexMap.keySet().stream()
                                    .filter(k -> StrUtil.containsAnyIgnoreCase(data.trim(), k.trim()))
                                    .forEach(k -> requiredIndexMap.put(k, finalRol));
                        } else {
                            String colName = requiredIndexMap.entrySet().stream().filter(e -> finalRol == e.getValue())
                                    .map(Map.Entry::getKey).findFirst().orElse("");
                            if (StringUtils.isNotEmpty(colName) && setMap.containsKey(colName)) {
                                setMap.get(colName).accept(data);
                            }
                        }
                    }

                    if (row > 0 && StrUtil.isAllNotEmpty(column.getColumnName(), column.getColumnType())) {
                        column.fillIsRequired(req.getRequiredFlag());
                        GenTableColumn columnPO = genTableAssembly.from(column);
                        columnPO.setSort(row + 1);
                        importTable.getGenTableColumnList().add(columnPO);
                    }

                    if(row == 0) {
                        // 校验指定的列名和表中列名是否存在
                        checkRequiredTableIndexMap(requiredIndexMap);
                    }
                }

                Map<String, Integer> requiredIndexTempMap = new HashMap<>();
                requiredIndexTempMap.put(req.getTableIndexCol(), -1);
                // 默认为联合索引
                List<GenTableIndex> tempGenTableIndexList = new ArrayList<>();
                for (int row = 0; row < maxRow; row++) {
                    GenTableIndex genTableIndex = new GenTableIndex();
                    int maxRol = wordTable.getRows().get(row).getCells().size();
                    Map<String, Consumer<String>> setMap = new HashMap<>();
                    setMap.put(req.getTableIndexCol(), genTableIndex::setColumnNames);
                    // 收集表索引字段
                    for (int rol = 0; rol < maxRol; rol++) {
                        String data = wordTable.getRows().get(row).getCells().get(rol).getText();
                        if (StringUtils.isEmpty(data)) {
                            continue;
                        }
                        int finalRol = rol;
                        if (row == 0) {
                            requiredIndexTempMap.keySet().stream()
                                    .filter(k -> StrUtil.containsAnyIgnoreCase(data.trim(), k.trim()))
                                    .forEach(k -> requiredIndexTempMap.put(k, finalRol));
                        } else {
                            String colName = requiredIndexTempMap.entrySet().stream().filter(e -> finalRol == e.getValue())
                                    .map(Map.Entry::getKey).findFirst().orElse("");
                            if (StringUtils.isNotEmpty(colName) && req.getTableIndexFlag().equalsIgnoreCase(data)
                                    && setMap.containsKey(colName)) {
                                setMap.get(colName).accept(importTable.getGenTableColumnList().get(row).getColumnName());
                                break;
                            }
                        }
                    }
                    if (StringUtils.isNotEmpty(genTableIndex.getColumnNames())) {
                        tempGenTableIndexList.add(genTableIndex);
                    }
                }
                // 合并联合索引
                List<String> columnNames = tempGenTableIndexList.stream()
                        .map(GenTableIndex::getColumnNames).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(columnNames)) {
                    GenTableIndex genTableIndex = new GenTableIndex();
                    genTableIndex.setName("idx_biz");
                    genTableIndex.setType(req.getIndexType());
                    genTableIndex.setMethod(req.getIndexMethod());
                    genTableIndex.setColumnNames(StringUtils.toStr(columnNames, ","));
                    importTable.getGenTableIndexList().add(genTableIndex);
                }
                importTableList.add(importTable);
            });


            importTableList.stream()
                    .flatMap(x -> x.getGenTableColumnList().stream())
                    .forEach(column -> {
                        ColumnUnit columnUnit = documentTableHandler.getColumnUnit(column.getColumnType());
                        genTableAssembly.updateGenTableColumn(columnUnit, column);
                    });
            return batchSaveGenTable(importTableList);
        } catch (IOException e) {
            log.error("errorMessage: {}", e.getMessage());
            log.debug("error: ", e);
            throw new BizException("导入失败");
        } finally {
            FileUtils.deleteFile(localFile.getAbsolutePath());
        }
    }

    public List<ImportTableResp> batchSaveGenTable(List<ImportTable> importTableList) {
        List<ImportTableResp> respList = new ArrayList<>();
        // 执行保存操作
        for (ImportTable item : importTableList) {
            GenTable genTable = item.getGenTable();
            List<GenTableColumn> genTableColumnList = item.getGenTableColumnList();
            genTableManager.saveImportTable(genTable);
            genTableColumnManager.saveColumnBatch(genTable, genTableColumnList);
            List<GenTableIndex> genTableIndexList = item.getGenTableIndexList().stream()
                    .peek(x -> x.setTableId(item.getGenTable().getId()))
                    .collect(Collectors.toList());
            genTableIndexManager.deleteByTableId(genTable.getId());
            genTableIndexManager.saveOrUpdateBatch(genTableIndexList);
            ImportTableResp resp = ImportTableResp.builder()
                    .tableId(genTable.getId())
                    .tableName(genTable.getTableName())
                    .tableComment(genTable.getTableComment()).build();
            respList.add(resp);
        }
        return respList;
    }

    @Override
    public void importTableFromExcel() {

    }

    @Override
    public Map<String, String> previewCode(List<Long> tableIds) {
        Map<String, String> dataMap = new HashMap<>();
        if (CollectionUtils.isEmpty(tableIds)) {
            return dataMap;
        }

        VelocityUtil.initVelocity();
        GenTable genTable = genTableManager.getById(tableIds.get(0));
        genTable.setColumns(genTableColumnManager.listByTableId(genTable.getId()));

        VelocityContext context = VelocityUtil.prepareContext(genTable);
        // 获取模板列表
        List<String> templates = VelocityUtil.getTemplateList(genTable.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, StandardCharsets.UTF_8.name());
            tpl.merge(context, sw);
            dataMap.put(template, sw.toString());
        }
        return dataMap;
    }

    @Override
    public IPage<GenTableDto> pageGenTable(GenTablePageReq req) {
        IPage<GenTable> page = genTableManager.pageGenTable(req);
        List<Long> datasourceIds = page.getRecords().stream().map(GenTable::getDatasourceId)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(datasourceIds)) {
            return new Page<>();
        }
        Map<Long, String> datasourceMap = datasourceService.listByIds(datasourceIds).stream()
                .collect(Collectors.toMap(DatasourceConfig::getId, DatasourceConfig::getName, (k1, k2) -> k1));
        List<GenTableDto> respList = page.getRecords().stream().map(genTableAssembly::toGenTableDto)
                .peek(x -> x.setDatasourceName(datasourceMap.get(x.getDatasourceId())))
                .collect(Collectors.toList());
        return new Page<GenTableDto>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(respList);
    }

    @Override
    public void syncDbTable(Long id) {
        GenTable genTable = this.getById(id);
        if (Objects.isNull(genTable)) {
            throw new BizException("生成表信息");
        }
        IGenTableService thisProxy = SpringUtils.getAopProxy(this);

        ImportDbTableReq importReq = new ImportDbTableReq();
        importReq.setDatasourceId(genTable.getDatasourceId());
        importReq.setTableNames(Collections.singletonList(genTable.getTableName()));
        thisProxy.importDbTable(importReq);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<ImportTableResp> importDbTable(ImportDbTableReq req) {
        List<SQLContext> contextList = this.datasourceService.listSqlContext(req.getDatasourceId(), req.getTableNames());
        List<ImportTable> importTableList = new ArrayList<>();
        List<GenTable> tableList = this.genTableManager.listByTableNames(req.getDatasourceId(), req.getTableNames());
        List<Long> tableIds = tableList.stream().map(GenTable::getId).collect(Collectors.toList());
        Map<String, GenTable> tableMap = tableList.stream().collect(Collectors.toMap(GenTable::getTableName, Function.identity()));

        List<GenTableColumn> tableColumns = genTableColumnManager.listByTableIds(tableIds);
        Map<Long, List<GenTableColumn>> columnMap = tableColumns.stream().collect(Collectors.groupingBy(GenTableColumn::getTableId));

        for (SQLContext sqlContext : contextList) {
            ImportTable importTable = new ImportTable();
            TableDefinition tableDefinition = sqlContext.getTableDefinition();

            String className = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableDefinition.getTableName());;

            GenTable genTable = tableMap.getOrDefault(tableDefinition.getTableName(), new GenTable());
            genTable.setTableName(tableDefinition.getTableName()).setDatasourceId(req.getDatasourceId())
                .setTplCategory(GenType.CRUD.getCode()).setClassName(className)
                .setDescription(tableDefinition.getComment())
                .setTableComment(tableDefinition.getComment()).setColumns(new ArrayList<>());

            List<GenTableColumn> oldColumnList = genTable.getId() == null ? Collections.emptyList() : columnMap.get(genTable.getId());
            Map<String, GenTableColumn> oldColumnMap = oldColumnList.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));

            for (ColumnDefinition item : tableDefinition.getColumnDefinitions()) {
                GenTableColumn tableColumn = oldColumnMap.getOrDefault(item.getColumnName(), new GenTableColumn());
                tableColumn.setColumnLength(item.getLength()).setColumnScale(item.getScale())
                    .setColumnName(item.getColumnName()).setColumnType(item.getType())
                    .setColumnComment(item.getComment()).setIsNullable(item.getIsNullable())
                    .setAttrName(StringUtils.firstNonEmpty(tableColumn.getAttrName(), item.getComment()))
                    .setSort(item.getSortNo()).setIsPk(item.getIsPk());
                importTable.getGenTableColumnList().add(tableColumn);
            }
            List<TableIndexDefinition> tableIndexDefinitions = tableDefinition.getTableIndexDefinitions();
            for (TableIndexDefinition item : tableIndexDefinitions) {
                importTable.getGenTableIndexList().add(genTableAssembly.toGenTableIndex(item));
            }
            importTable.setGenTable(genTable);
            importTableList.add(importTable);
        }
        return batchSaveGenTable(importTableList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteGenTableByIds(List<Long> tableIds) {
        if (!genTableManager.removeByIds(tableIds)) {
            throw new BizException("删除表数据失败");
        }
        genTableColumnManager.removeByTableIds(tableIds);
        genTableIndexManager.deleteBatchByTableIds(tableIds);
    }

    @Override
    public GenTableDto getGenTableRespByTableId(Long tableId) {
        GenTable genTable = this.getById(tableId);
        Assert.notNull(genTable, "表不存在");
        return genTableAssembly.toGenTableDto(genTable);
    }

    @Override
    @Transactional
    public void saveOrUpdateGenTableInfo(GenTableInfoSaveReq req) {
        if (Objects.nonNull(req.getGenTable()) && Objects.nonNull(req.getGenTable().getId())) {
            this.updateById(req.getGenTable());
        }
        genTableColumnManager.saveOrUpdateBatch(req.getColumnList());

        List<GenTableIndexReq> indexList = req.getIndexList();
        List<GenTableIndex> genTableIndexList = null;
        if (CollectionUtils.isNotEmpty(indexList)) {
            genTableIndexList = indexList.stream().map(genTableAssembly::toGenTableIndex).collect(Collectors.toList());
         }
        genTableIndexManager.saveOrUpdateBatch(genTableIndexList);
    }

    @Override
    public GenInfoResp getGenInfo(Long tableId) {
        GenTable genTable = this.getById(tableId);
        if (Objects.isNull(genTable)) {
            return null;
        }
        DatasourceConfig datasourceConfig = datasourceService.getById(genTable.getDatasourceId());
        GenInfoResp resp = genTableAssembly.toGenInfoDto(genTable);

        Set<UiSelectOption> packages = this.baseMapper.listPackageNames(genTable.getDatasourceId())
            .stream().filter(StringUtils::isNotEmpty).map(UiSelectOption::new).collect(Collectors.toSet());

        Set<UiSelectOption> authorList = this.baseMapper.listAuthor(genTable.getDatasourceId())
            .stream().filter(StringUtils::isNotEmpty).map(UiSelectOption::new).collect(Collectors.toSet());

        resp.setAuthorList(authorList);
        resp.setPackageNameList(packages);
        return resp;
    }

    @Override
    public GenTable updateGenTable(GeneratorReq req) {
        if (!this.updateById(genTableAssembly.toGenInfo(req))) {
            throw new BizException("代码生成信息更新失败");
        }
        GenTable genTable = this.getById(req.getId());
        if (Objects.isNull(genTable)) {
            throw new BizException("代码生成信息不存在, 请重新生成");
        }
        return genTable;
    }
}
